import types
import Assert
import exceptions
import traceback
import time
	
class SuiteRunner():
	def __init__(self, package_names):
		self.package_names = package_names
		self.clock = SystemClock()
		
	def run(self):
		suite_result = SuiteResult()

		start_time = self.clock.time()
		for package_name in self.package_names:
			self.run_package(package_name, suite_result)
		finish_time = self.clock.time()		
		
		suite_result.total_run_time = finish_time - start_time
		
		return suite_result
	
	def run_package(self, package_name, suite_result):
		package = self.import_package(package_name)
		for object_name in dir(package):
			object = getattr(package, object_name)
			if (type(object) == types.ModuleType):
				suite_result.add_result(ModuleRunner(object).run())
	
	def import_package(self, package_name):
		__import__(package_name, globals(), locals(), ['*'], -1)
		return __import__(package_name)

class SystemClock():
	def time(self):
		return time.time()	

class ModuleRunner():
	def __init__(self, module_object):
		self.module_object = module_object
		
	def run(self):
		results = ModuleResult(self.module_object.__name__)
		for item in dir(self.module_object):
			if (self.is_test_fixture(item)):
				fixture = eval(self.create_full_name(item) + "()")
				results.add_result(FixtureRunner(fixture).run())
		return results
		
	def is_test_fixture(self, item):
		return type(eval(self.create_full_name(item))) == types.ClassType and item.endswith("Tests")
		
	def create_full_name(self, item):
		return "self.module_object." + item

class FixtureRunner():
	def __init__(self, test_instance):
		self.test_instance = test_instance

	def run(self):
		results = FixtureResult(self.test_instance.__class__.__name__)
		method_names = dir(self.test_instance)
		for method_name in method_names:
			if (method_name.startswith("test")):
				results.add_test_result(self.run_test_method(method_name))
		return results
		
	def run_test_method(self, method_name):
		# IMPORTANT: We must write a failing test before changing this method and see the method fails after the change
		try:
			try:
				self.invoke_if_exists("set_up")
				getattr(self.test_instance, method_name)()
			finally:
				self.invoke_if_exists("tear_down")
			return TestResult.create_success(method_name)
		except Assert.AssertionException, exception:
			return TestResult.create_fail(method_name, exception.message, traceback.format_exc())
		except Assert.IgnoreException, exception:
			return TestResult.create_ignore(method_name, exception.message)
		except exceptions.Exception, exception:
			return TestResult.create_fail(method_name, ("%s : %s" % (exception.__class__.__name__, exception.message)), traceback.format_exc())
			
	def invoke_if_exists(self, method_name):
		if (hasattr(self.test_instance, method_name)):
			getattr(self.test_instance, method_name)()

class TestResult():
	def __init__(self, method_name, is_successful, message="", stack_trace="", is_ignored=False):
		self.method_name = method_name
		self.is_successful = is_successful
		self.message = message
		self.stack_trace = stack_trace
		self.is_ignored = is_ignored
	
	def create_success(method_name):
		return TestResult(method_name, True)
	create_success = staticmethod(create_success)
	
	def create_fail(method_name, message, stack_trace):
		return TestResult(method_name, False, message, stack_trace)
	create_fail = staticmethod(create_fail)
	
	def create_ignore(method_name, message):
		return TestResult(method_name, False, message, "", True)
	create_ignore = staticmethod(create_ignore)
	
	def __str__(self):
		return "%s %s" % (self.method_name, self.message)
		
	def to_json(self):
		return {"method_name" : self.method_name,
								"is_successful" : self.is_successful,
								"message" : self.message,
								"stack_trace" : self.stack_trace,
								"is_ignored" : self.is_ignored}

class FixtureResult():
	def __init__(self, fixture_name):
		self.fixture_name = fixture_name
		self.test_results = []
	
	def is_successful(self):
		return self.failing_test_count() == 0
	
	def add_test_result(self, test_result):
		self.test_results.append(test_result)
		
	def total_test_count(self):
		return len(self.test_results)
	
	def passing_test_count(self):
		passing_test_count = 0
		for result in self.test_results:
			if result.is_successful:
				passing_test_count += 1
		return passing_test_count
		
	def failing_test_count(self):
		return self.total_test_count() - self.passing_test_count() - self.ignored_test_count()
		
	def ignored_test_count(self):
		ignored_test_count = 0
		for result in self.test_results:
			if result.is_ignored:
				ignored_test_count += 1
		return ignored_test_count
		
	def to_json(self):
		test_results_json = []
		for test_result in self.test_results:
			test_results_json.append(test_result.to_json())
		
		return {"fixture_name" : self.fixture_name,
				"test_results" : test_results_json,
				"is_successful" : self.is_successful()}
		
class ModuleResult():
	def __init__(self, module_name=''):
		self.module_name = module_name
		self.fixture_results = []
		
	def add_result(self, fixture_result):
		self.fixture_results.append(fixture_result)
		
	def is_successful(self):
		for fixture_result in self.fixture_results:
			if fixture_result.is_successful() == False:
				return False
		return True
		
	def total_test_count(self):
		return calculate_total(self.fixture_results, 'total_test_count')
	
	def passing_test_count(self):
		return calculate_total(self.fixture_results, 'passing_test_count')
	
	def failing_test_count(self):
		return self.total_test_count() - self.passing_test_count() - self.ignored_test_count()
	
	def ignored_test_count(self):
		return calculate_total(self.fixture_results, 'ignored_test_count')
		
	def to_json(self):		
		fixture_results_json = []
		for fixture_result in self.fixture_results:
			fixture_results_json.append(fixture_result.to_json())
		
		return{"module_name" : self.module_name,
		"fixture_results" : fixture_results_json,
		"is_successful" : self.is_successful()}
		
class SuiteResult():
	def __init__(self):		
		self.module_results = []
		self.total_run_time = 0
		
	def add_result(self, module_result):
		self.module_results.append(module_result)
		
	def is_successful(self):
		for module_result in self.module_results:
			if (module_result.is_successful() == False):
				return False
				
		return True			
		
	def total_test_count(self):
		return calculate_total(self.module_results, 'total_test_count')		
	
	def passing_test_count(self):
		return calculate_total(self.module_results, 'passing_test_count')
		
	def failing_test_count(self):
		return self.total_test_count() - self.passing_test_count() - self.ignored_test_count()
	
	def ignored_test_count(self):
		return calculate_total(self.module_results, 'ignored_test_count')
		
	def to_json(self):
		module_results_json = []
		for module_result in self.module_results:
			module_results_json.append(module_result.to_json())		
		
		jsonDictionary = { 
						"is_successful" : self.is_successful(),
						"total_test_count" : self.total_test_count(),
						"passing_test_count" : self.passing_test_count(),
						"failing_test_count" : self.failing_test_count(),
						"ignored_test_count" : self.ignored_test_count(),
						"total_run_time" : self.total_run_time,
						"module_results" : module_results_json
						}
		
		return jsonDictionary
		
		
def calculate_total(collection, count_method_name):
	total = 0
	for element in collection:
		total += getattr(element, count_method_name)()
		
	return total